-- 2022-8-18

---[[ 算术运算相关的元方法

    -- 在本节中 我们将介绍一个解释元表基础的示例 假设有一个用表来表示集合的模块 该模块还有一些用来计算集合并集和交集等的函数 可以参见示例20.1

    -- 示例20.1 一个用于集合的简单模块
    local Set = {}
    function Set.new(l)
        local set = {}
        for _, v in ipairs(l) do
            set[v] = true
        end
        return set
    end
    function Set.union(a,b)
        local res = Set.new{}
        for k in pairs(a) do
            res[k] = true
        end
        for k in pairs(b) do
            res[k] = true
        end
        return res
    end
    function Set.intersection(a,b)
        local res = Set.new{}
        for k in pairs(a) do
            res[k] = b[k]
        end
        return res
    end
    -- 将集合表示为字符串
    function Set.tostring(set)
        local l = {} -- 保存集合中所有元素的列表
        for e in pairs(set) do
            l[#l+1] = tostring(e)
        end
        return "{" .. table.concat(l,", ") .. "}"
    end
    -- return Set

    -- 现在 假设想使用加法操作符来计算两个集合的并集 那么可以让所有表示集合的表共享一个元表 这个元表中定义了这些表应该如何执行加法操作
    -- 首先 我们创建一个普通的表 这个表被用作集合的元表:
    local mt = {} -- 集合的元表
    -- 然后 修改用于创建集合的函数Set.new 在新版本中只多了一行 即将mt设置为函数Set.new所创建的表的元表:
    function Set.new(l) -- 第二个版本
        local set = {}
        setmetatable(set,mt)
        for _, V in ipairs(L) do
            set[v] = true
        end
        return set
    end
    -- 在此之后 所有由Set.new创建的集合都具有了一个相同的元表:
    s1 = Set.new{10,20,30,50}
    s2 = Set.new{30,1}
    print(getmetatable(s1)) --> table:0x00672B60
    print(getmetatable(s2)) --> table:0x00672B60
    -- 最后 向元表中加入元方法(metamethod)__add 也就是用于描述如何完成加法的字段:
    mt.__add = Set.union
    -- 此后 只要Lua语言试图将两个集合相加 它就会调用函数Set.union 并将两个操作数作为参数传入
    -- 通过元方法 我们就可以使用加法运算符来计算集合的并集了:
    s3 = s1 + s2
    print(Set.tostring(s3)) -- {1,10,20,30,50}
    -- 类似地 还可以使用乘法运算符来计算集合的交集
    mt.__mul = Set.intersection
    print(Set.tostring((s1+s2)*s1)) --> {10,20,30,50}
    -- 每种算术运算符都有一个对应的元方法 除了加法和乘法外 还有减法(__sub) 除法(__div) floor除法(__idiv) 复数(__unm) 取模(__mod)和幂运算(__pow)
    -- 类似地 位操作也有元方法:按位与(__band) 按位或(__bor) 按位异或(__bxor) 按位取反(__bnot) 向左位移(__shl)和向右移位(__shr)
    -- 我们还可以使用字段__concat来定义连接运算符的行为

    -- 当我们把两个集合相加时 使用哪个元表是确定的 然而 当一个表达式中混合了两种具有不同的元表的值时 例如
    s = Set.new{1,2,3}
    s = s + 8
    -- Lua语言会按照如下步骤来查找元方法:如果第一个值有元表且元表中存在所需的元方法 那么Lua语言就使用这个元方法 与第二个值无关
    -- 如果第二个值有元表且元表中存在所需的元方法 Lua语言就使用这个元方法 否则 Lua语言就抛出异常
    -- 因此 上例会调用Set.union 而表达式10+s和"hello"+s同理(由于数值和字符串都没有元方法__add)

    -- Lua语言不关心这些混合类型 但我们在实现中需要关心混合类型 如果我们执行了s = s + 8 那么在Set.union内部就会发生错误:
    -- bad argument #1 to 'pairs' (table expected, got number)
    -- 如果想要得到更明确的错误信息 则必须在试图进行操作前显式地检查操作数的类型 例如:
    function Set.union(a,b)
        if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
            error("attempt to 'add' a set with a non-set value",2)
        end
    end
    -- 同前
    -- 请注意 函数error的第二个参数(上例中的2)说明了出错的原因位于调用该函数的代码中
--]]